{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0a5b6e8f-829d-4ebc-a07f-4b73bc3028f4",
   "metadata": {},
   "source": [
    "# Geographic Options\n",
    "\n",
    ":::{note}\n",
    "Most of the options below require the [GeoViews](https://geoviews.org) library to be installed. One notable exception is the [`tiles`](option-tiles) option, which, if the data is in *lat/lon* coordinates (e.g. GPS data) or already in the *Web Mercator* coordinate reference system, allows you to overlay the data on a web tiled map without requiring GeoViews.\n",
    ":::\n",
    "\n",
    ":::{admonition} Supported Plot Types\n",
    "\n",
    "Only certain plot types support geographic coordinates, currently including: {meth}`hvplot.hvPlot.points`, {meth}`hvplot.hvPlot.paths`, {meth}`hvplot.hvPlot.polygons`, {meth}`hvplot.hvPlot.image`, {meth}`hvplot.hvPlot.quadmesh`, {meth}`hvplot.hvPlot.contour`, and {meth}`hvplot.hvPlot.contourf`.\n",
    ":::\n",
    "\n",
    "Options for geographic plots, including map projections, tile overlays, and geographic features like coastlines and borders:\n",
    "\n",
    "```{eval-rst}\n",
    ".. plotting-options-table:: Geographic Options\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ca798a0-f299-414f-bc87-12c0ea203757",
   "metadata": {},
   "source": [
    "(option-coastline)=\n",
    "## `coastline`\n",
    "\n",
    "The `coastline` option overlays coastlines on the plot. You can set `coastline=True` to use the default scale (`'110m'`), or specify one of the available resolution strings: `'10m'`, `'50m'`, or `'110m'`. The dataset originates from [Natural Earth](https://www.naturalearthdata.com/features/).\n",
    "\n",
    "Enabling this option implies `geo=True` and requires [GeoViews](https://geoviews.org) to be installed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "297ea059-4f44-42b5-9d49-9a1884ccef43",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', frame_width=250, alpha=0.5)\n",
    "df.hvplot.points(coastline=True, title=\"Coastline=True\", **plot_opts) +\\\n",
    "df.hvplot.points(coastline='50m', title='Coastline=50m', **plot_opts)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47e112f9-fd34-4d58-8feb-b0611f3a049c",
   "metadata": {},
   "source": [
    "(option-crs)=\n",
    "## `crs`\n",
    "\n",
    "The `crs` option sets the [Coordinate Reference System](https://en.wikipedia.org/wiki/Spatial_reference_system) (CRS) of the data (the input/source projection). It accepts a variety of values, including:\n",
    "\n",
    "- {class}`cartopy:cartopy.crs.CRS` instance (e.g. {class}`cartopy:cartopy.crs.PlateCarree`) or class name (e.g. `'PlateCarree'`)\n",
    "- [EPSG code](https://en.wikipedia.org/wiki/EPSG_Geodetic_Parameter_Dataset) string or integer (e.g. `'EPSG:4326'`, `4326`)\n",
    "- {class}`pyproj:pyproj.crs.CRS` or {class}`pyproj:pyproj.Proj` instances\n",
    "- PROJ.4 string\n",
    "- [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) string\n",
    "\n",
    "If not provided, [PlateCarree](https://en.wikipedia.org/wiki/Equirectangular_projection) (lat/lon) is assumed when `geo=True`.\n",
    "\n",
    "Enabling this option implies `geo=True` and requires [GeoViews](https://geoviews.org) to be installed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ac96974-7f01-4bf7-822c-d3627dcac793",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "import cartopy.crs as ccrs\n",
    "import pyproj\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', coastline=True, frame_width=250, alpha=0.5)\n",
    "\n",
    "(\n",
    "    df.hvplot.points(crs=ccrs.PlateCarree(), title=\"cartopy.PlateCarree instance\", **plot_opts) +\n",
    "    df.hvplot.points(crs='PlateCarree', title=\"PlateCarree string\", **plot_opts) +\n",
    "    df.hvplot.points(crs='EPSG:4326', title=\"EPSG:4326 string\", **plot_opts) +\n",
    "    df.hvplot.points(crs=pyproj.CRS(4326), title=\"pyproj.CRS 4326 instance\", **plot_opts)\n",
    ").cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4d4d9cf-fe86-484e-99e5-28a466eca40d",
   "metadata": {},
   "source": [
    ":::{seealso}\n",
    "The [`projection`](option-projection) option to set the output/target projection.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f76fbab-3df1-4727-a85c-48fcdeeaa40b",
   "metadata": {},
   "source": [
    "(option-features)=\n",
    "## `features`\n",
    "\n",
    "The `features` option overlays additional geographic features such as land, ocean, rivers, and borders. These features originate from the [Natural Earth](https://www.naturalearthdata.com/features/) dataset.\n",
    "\n",
    "It accepts:\n",
    "- A list of feature names\n",
    "- A dictionary with feature names as keys and resolution strings as values\n",
    "\n",
    "Available features: `'borders'`, `'coastline'`, `'lakes'`, `'land'`, `'ocean'`, `'rivers'`, `'states'`.\n",
    "Available scales: `'10m'`, `'50m'`, `'110m'`.\n",
    "\n",
    "`'land'` and `'ocean'` features are underlaid, other features are overlaid.\n",
    "\n",
    ":::{important}\n",
    "This option requires a live internet connection to download Natural Earth datasets.\n",
    ":::\n",
    "\n",
    "Enabling this option implies `geo=True` and requires [GeoViews](https://geoviews.org) to be installed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d9fcb88b-8442-4864-8c7e-11ea6de636fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', frame_width=250, alpha=0.5)\n",
    "df.hvplot.points(\n",
    "    features=['ocean', 'land'],\n",
    "    title=\"Features: ocean + land\", **plot_opts\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    features={'ocean': '50m', 'land': '50m'},\n",
    "    title=\"Features with resolution\", **plot_opts\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "579dd50c-4e14-41ef-bc75-4e910656a4b7",
   "metadata": {},
   "source": [
    "(option-geo)=\n",
    "## `geo`\n",
    "\n",
    "The `geo` option declares that the plot is geographic and enables plotting with [GeoViews](https://geoviews.org), which is required to be installed.\n",
    "\n",
    "When `geo=True` and without further definition, the input data is assumed to be in lat/lon coordinates. When not set explicitly, the [`data_aspect`](option-data_aspect) option is set to `1`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f10c599-9db7-4045-aefb-c743db296ac5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', frame_width=250, alpha=0.5)\n",
    "plot = df.hvplot.points(title=\"geo=False\", **plot_opts)\n",
    "plot_geo = df.hvplot.points(geo=True, title=\"geo=True\", **plot_opts)\n",
    "(plot + plot_geo).opts(shared_axes=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c2575c8-73f5-4cd4-a4d0-93b2257806c4",
   "metadata": {},
   "source": [
    "The object returned by the `df.hvplot.points()` call is a GeoViews `Points` element."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10499099-cb32-449c-8dd5-f9ac8f4fa168",
   "metadata": {},
   "outputs": [],
   "source": [
    "type(plot_geo)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f17a82c0-04ff-4c31-9361-bb9d97fc3a3c",
   "metadata": {},
   "source": [
    "(option-global_extent)=\n",
    "## `global_extent`\n",
    "\n",
    "The `global_extent` option expands the plot extent to span the full globe. This is useful when your data covers only part of the globe, but you still want to display the full world extent.\n",
    "\n",
    "Enabling this option implies `geo=True` and requires [GeoViews](https://geoviews.org) to be installed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f9d2f15-6623-49ee-ba52-2b2bf700137e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='lon',\n",
    "    y='lat',\n",
    "    frame_height=200,\n",
    "    global_extent=True,\n",
    "    coastline=True,\n",
    "    title=\"Global Extent Enabled\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a809094-4fd6-4260-8c93-7dceee7b7c3f",
   "metadata": {},
   "source": [
    "(option-project)=\n",
    "## `project`\n",
    "\n",
    "The `project` option projects the data to the output/target projection before applying operations enabled by the [`datashade`](option-datashade) or [`rasterize`](option-rasterize) options. This can greatly improve interactivity when one of these operations is enabled, avoiding to re-project the data when panning/zooming.\n",
    "\n",
    "Enabling this option implies `geo=True` and requires [GeoViews](https://geoviews.org) to be installed."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85cb61a9-ebcb-4894-a0de-39bff4df26f9",
   "metadata": {},
   "source": [
    "(option-projection)=\n",
    "## `projection`\n",
    "\n",
    "The `projection` option sets the **display CRS** of the plot (the output/target projection).\n",
    "\n",
    "Accepts the same values as [`crs`](option-crs), including:\n",
    "- {class}`cartopy:cartopy.crs.CRS` instance (e.g. {class}`cartopy:cartopy.crs.PlateCarree`) or class name (e.g. `'PlateCarree'`)\n",
    "- [EPSG code](https://en.wikipedia.org/wiki/EPSG_Geodetic_Parameter_Dataset) string or integer (e.g. `'EPSG:4326'`, `4326`)\n",
    "- {class}`pyproj:pyproj.crs.CRS` or {class}`pyproj:pyproj.Proj` instances\n",
    "- PROJ.4 string\n",
    "- [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) string\n",
    "\n",
    "If not specified, defaults to [PlateCarree](https://en.wikipedia.org/wiki/Equirectangular_projection), or to [Web Mercator](https://en.wikipedia.org/wiki/Web_Mercator_projection) when [`tiles=True`](option-tiles).\n",
    "\n",
    "Enabling this option implies `geo=True` and requires [GeoViews](https://geoviews.org) to be installed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91a107e2-5801-410f-9419-182cac0de6d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cartopy.crs as ccrs\n",
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', coastline=True, alpha=0.5)\n",
    "layout = df.hvplot.points(\n",
    "    frame_width=250,\n",
    "    geo=True,\n",
    "    global_extent=True,\n",
    "    title='Default display CRS: PlateCarree',\n",
    "    **plot_opts\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    frame_width=200,\n",
    "    projection=ccrs.Orthographic(125, 0),\n",
    "    global_extent=True,\n",
    "    title='Orthographic Projection',\n",
    "    **plot_opts\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    frame_height=250,\n",
    "    projection='EPSG:32651',\n",
    "    title='UTM 51N',\n",
    "    **plot_opts\n",
    ")\n",
    "layout.opts(shared_axes=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9428dcff-3704-4c21-ae79-e8d88ef33d6c",
   "metadata": {},
   "source": [
    ":::{seealso}\n",
    "The [`crs`](option-crs) option to define the source CRS.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdc6fcbc-eca7-4e06-bcfc-9360f7409477",
   "metadata": {},
   "source": [
    "(option-tiles)=\n",
    "## `tiles`\n",
    "\n",
    ":::{tip}\n",
    "This option can be used without having to install GeoViews to plot data in lat/lon or Web Mercator coordinates.\n",
    ":::\n",
    "\n",
    "The `tiles` option adds a [tiled web map](https://en.wikipedia.org/wiki/Tiled_web_map), or tile map, as a basemap layer. It accepts:\n",
    "\n",
    "- `True`: defaults to the [OpenStreetMap](https://en.wikipedia.org/wiki/OpenStreetMap) layer\n",
    "- `xyzservices.TileProvider` instance (e.g. `xyz.Esri.WorldPhysical`). The [`xyzservices`](https://xyzservices.readthedocs.io) library gives easy access to [hundreds of tiled web maps](https://xyzservices.readthedocs.io/en/stable/gallery.html).\n",
    "- HoloViews/GeoViews tile name strings (e.g. `'EsriTerrain'`). See the list below of tiles available as a string\n",
    "- [`holoviews.Tiles`](https://holoviews.org/reference/elements/bokeh/Tiles.html) class or instance (e.g. `hv.element.tiles.CartoDark`)\n",
    "- [`geoviews.WMTS`](https://geoviews.org/user_guide/Working_with_Bokeh.html#wmts-tile-sources) class or instance (e.g. `gv.tile_sources.CartoDark`)\n",
    "\n",
    ":::{important}\n",
    "This option requires a live internet connection to download the web tiles on the fly (zooming/panning).\n",
    ":::\n",
    "\n",
    "\n",
    "When `tiles` is enabled, the target [`projection`](option-projection) is internally set to Web Mercator (`EPSG:3857`) as this is the only projection supported by tiled web maps.\n",
    "\n",
    "hvPlot can display the data overlaid on a tile map without having to set `geo=True` and rely on GeoViews:\n",
    "- If the data is already in the Web Mercator CRS (easting (X) /northing (Y) metric units), as no projection is needed.\n",
    "- If the coordinate values fall within lat/lon bounds, the data is auto-projected (except for lazy data objects) to Web Mercator. This behavior can be disabled by setting the [`projection`](option-projection) to `False`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3ab1697-e73b-40d1-b1ce-3da91cf191e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import geoviews as gv\n",
    "import holoviews as hv\n",
    "\n",
    "print(\"HoloViews tiles:\", *hv.element.tiles.tile_sources, sep=\" \", end=\"\\n\\n\")\n",
    "print(\"GeoViews tiles:\", *gv.tile_sources.tile_sources, sep=\" \")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1bfe5698-c665-42fd-af05-082f04e8fbbb",
   "metadata": {},
   "source": [
    "First we'll set `geo=True` together with `tiles` to enable plotting with GeoViews."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b78d812-f8ce-4ac5-ab7a-16e8442e1648",
   "metadata": {},
   "outputs": [],
   "source": [
    "import geoviews as gv\n",
    "import hvplot.pandas  # noqa\n",
    "import xyzservices.providers as xyz\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(geo=True, x='lon', y='lat', alpha=0.2, c='brown', frame_width=250)\n",
    "layout = (\n",
    "    df.hvplot.points(tiles=True, title=\"Default to OpenStreetMap\", **plot_opts) +\n",
    "    df.hvplot.points(tiles=xyz.Esri.WorldPhysical, title=\"xyz.Esri.WorldPhysical\", **plot_opts) +\n",
    "    df.hvplot.points(tiles='EsriTerrain', title=\"EsriTerrain string\", **plot_opts) +\n",
    "    df.hvplot.points(tiles=gv.tile_sources.EsriImagery, title=\"GeoViews WMTS\", **plot_opts)\n",
    ")\n",
    "layout.cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5e3da05-760b-4198-bd2f-8ea0ca44c9fa",
   "metadata": {},
   "source": [
    "We now create the same plots but without `geo=True`. The dataset contains lat/lon coordinates, that hvPlot will automatically project to Web Mercator. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ccb5ed4f-d0c7-4889-8ef2-449395124f93",
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "import hvplot.pandas  # noqa\n",
    "import xyzservices.providers as xyz\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', alpha=0.2, c='brown', frame_width=250)\n",
    "layout = (\n",
    "    df.hvplot.points(tiles=True, title=\"Default: OpenStreetMap\", **plot_opts) +\n",
    "    df.hvplot.points(tiles=xyz.Esri.WorldPhysical, title=\"xyz.Esri.WorldPhysical\", **plot_opts) +\n",
    "    df.hvplot.points(tiles='EsriTerrain', title=\"EsriTerrain string\", **plot_opts) +\n",
    "    df.hvplot.points(tiles=hv.element.tiles.EsriImagery, title=\"HoloViews Tiles\", **plot_opts)\n",
    ")\n",
    "layout.cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcc48e78",
   "metadata": {},
   "source": [
    "If `xlim` and `ylim` are in `lat`/`lon` coordinates, they will be automatically transformed to Web Mercator coordinates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0f86e368",
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "import hvplot.pandas  # noqa\n",
    "import xyzservices.providers as xyz\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='lon', y='lat', alpha=0.2, c='brown', frame_width=250, xlim=(-130, -70), ylim=(30, 60))\n",
    "layout = (\n",
    "    df.hvplot.points(tiles=True, title=\"Default: OpenStreetMap\", **plot_opts) +\n",
    "    df.hvplot.points(tiles=xyz.Esri.WorldPhysical, title=\"xyz.Esri.WorldPhysical\", **plot_opts) +\n",
    "    df.hvplot.points(tiles='EsriTerrain', title=\"EsriTerrain string\", **plot_opts) +\n",
    "    df.hvplot.points(tiles=hv.element.tiles.EsriImagery, title=\"HoloViews Tiles\", **plot_opts)\n",
    ")\n",
    "layout.cols(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0db0527-bb56-4e99-8864-22819b6367c3",
   "metadata": {},
   "source": [
    ":::{tip}\n",
    "When dealing with large datasets meant to be overlaid on a tile map, it may be appropriate to project them to Web Mercator beforehand. HoloViews provides a simple utility [function](inv:holoviews#holoviews.util.transform.lon_lat_to_easting_northing) to convert lat/lon coordinates to Web Mercator northing/easting metric values. See the example below.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00f95f04-8f3a-458c-8440-8309d20259a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "from holoviews.util.transform import lon_lat_to_easting_northing\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "df['x'], df['y'] = lon_lat_to_easting_northing(df['lon'], df['lat'])\n",
    "print(df[['lat', 'lon', 'x', 'y']].head(2))\n",
    "df.hvplot.points(x='x', y='y', alpha=0.2, c='brown', frame_width=250, tiles=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9c94396-02e6-4a3c-92dc-ca28e359d095",
   "metadata": {},
   "source": [
    "[GeoPandas GeoDataFrame](https://geopandas.org/en/stable/docs/reference/geodataframe.html) objects can also be overlaid on a tile map without GeoViews, like in the example below, where a dataset in the *UTM 51N* CRS is plotted (hvPlot internally converting the data to Web Mercator)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c7286032-5d00-41a5-9ccb-2b183510db75",
   "metadata": {},
   "outputs": [],
   "source": [
    "import geopandas as gpd\n",
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "gdf = gpd.GeoDataFrame(\n",
    "    df,\n",
    "    geometry=gpd.points_from_xy(df['lon'], df['lat']),\n",
    "    crs=\"EPSG:4326\"\n",
    ").to_crs(\"EPSG:32651\")  # UTM 51N\n",
    "\n",
    "gdf.hvplot.points(alpha=0.2, c='brown', frame_width=250, tiles=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9208b284-fc6f-49be-8a79-13d00a8d4637",
   "metadata": {},
   "source": [
    "(option-tiles_opts)=\n",
    "## `tiles_opts`\n",
    "\n",
    "The `tiles_opts` option is a dictionary of style properties applied to the tiles layer. Requires [`tiles`](option-tiles) to be set.\n",
    "\n",
    "Common keys include: `alpha`, `width`, `height`, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ffa119f3-a144-46bf-86c0-3aa7c88d403a",
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.earthquakes(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='lon', y='lat', frame_width=300, c='mag',\n",
    "    tiles=True, tiles_opts={'alpha': 0.3},\n",
    "    title=\"Tile Layer with Alpha\"\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
